#ifndef __BASIC_MEMORY_POOL_H__
#define __BASIC_MEMORY_POOL_H__

#include <memory>
#include "basic/platform.h"
#include "basic/types.h"
#include "basic/log.h"

namespace basic {
class DKMemoryAllocator;

template <typename T>
struct MemoryPointer {
  MemoryPointer() {
  }
  MemoryPointer(T* p, unsigned long long len) : position(p), length(len) {
  }
  ~MemoryPointer() {
    length = 0u;
    position = nullptr;
  }

  T* position{nullptr};
  unsigned long long length{0u};
};

M_API_FUNC class DKMemoryAllocator {
  M_DELETE_COPY_ASSIGN(DKMemoryAllocator)

  class _MemoryBlock final {
    M_DELETE_COPY_ASSIGN(_MemoryBlock)

  public:
    _MemoryBlock() = delete;
    _MemoryBlock(unsigned long long); /// size in bytes;
    ~_MemoryBlock();
    inline unsigned long long GetPreservingSize() const {
      return m_data.length;
    }

    bool Resize(unsigned long long size) {
      if (size != m_data.length) {
        if (m_data.position) {
          M_SAFE_FREE(m_data.position)
          m_data.position = nullptr;
        }
        m_data.position = decltype(m_data.position) malloc(size * sizeof(T));
        m_data.length = size;
        return m_data.position != nullptr;
      }
      return false;
    }

  private:
    friend class DKMemoryAllocator;
    MemoryPointer<unsigned char> m_data;
  };

public:
  DKMemoryAllocator() = default;
  ~DKMemoryAllocator() = default;
  /**
   * takes ownership of pb
   */
  void SetMemoryBlock(_MemoryBlock* pb);
  void SetMemoryBlock(std::shared_ptr<_MemoryBlock> b, const DKSRange<unsigned long long>&& r);
  /// allocate `n` T instances;
  template <typename T>
  T* Allocate(unsigned long long n) {
    if (n <= 0) {
      M_LOG_WARN("allocating count MUST bigger than 0, argument is:", n);
      return nullptr;
    }

    const unsigned long long _l{sizeof(T) * n};
    const unsigned long long _e{m_pivot + _l};
    if (_e > m_range.length) {
      M_LOG_FATAL("allocating space in block-range was failed! range begin:", m_range.begin,
                  ", length:", m_range.length, "bytes, allocating begin:", m_pivot, ", length:", _l, "bytes")
      return nullptr;
    }

    T* r = new (m_ptrBlock->m_pBuffer + m_range.begin + m_pivot) T[n];
    m_pivot = _e;
    return r;
  }
  void FreeAll() {
    m_pivot = 0u;
  }

private:
  std::shared_ptr<_MemoryBlock> m_ptrBlock;
  DKSRange<unsigned long long> m_range;
  unsigned long long m_pivot{0u};
};
} // namespace basic

#endif
